package com.report.service.impl;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.Ipv4Util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.zhxu.bs.BeanSearcher;
import cn.zhxu.bs.SearchResult;
import cn.zhxu.bs.operator.Contain;
import cn.zhxu.bs.operator.Equal;
import cn.zhxu.bs.util.MapBuilder;
import cn.zhxu.bs.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.report.constants.TagConstants;
import com.report.dto.request.*;
import com.report.dto.response.*;
import com.report.dto.response.zu.ReportItemRes;
import com.report.entity.Collect;
import com.report.entity.Member;
import com.report.enums.FileEnum;
import com.report.mapper.MemberMapper;
import com.report.service.IBrowseService;
import com.report.service.ICollectService;
import com.report.service.IMemberService;
import com.report.service.MemberUploadService;
import com.report.utils.LocalDateUtils;
import com.zmh.framework.constants.Constants;
import com.zmh.framework.enums.ResultCodeEnum;
import com.zmh.framework.exception.ResultException;
import com.zmh.framework.oss.OssConfigure;
import com.zmh.framework.redis.RedisCache;
import com.zmh.framework.sys.entity.SysLog;
import com.zmh.framework.sys.service.SysLogService;
import com.zmh.framework.util.IpKit;
import com.zmh.framework.util.MD5;
import com.zmh.framework.util.Result;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author summer
 * @since 2024/06/25 14:54:50
 */
@Service
@Transactional(rollbackFor = Exception.class)
@AllArgsConstructor
@Slf4j
public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements IMemberService {

    private final RedisCache redisCache;


    @Override
    public Page<MemberRes> lists(MemberReq memberReq) {
        Page<MemberRes> page=new Page<>(memberReq.getPageNum(), memberReq.getPageSize());
        Map<String, Object> build = MapUtils.builder().page(memberReq.getPageNum() - 1, memberReq.getPageSize())
                .field(MemberRes::getNickName, memberReq.getName()).op(Contain.class)
                .field(MemberRes::getPhone,memberReq.getPhone()).op(Contain.class)
                .field(MemberRes::getIdNum,memberReq.getIdNum()).build();
        SearchResult<MemberRes> search = beanSearcher.search(MemberRes.class, build);
        page.setRecords(search.getDataList());
        page.setTotal(search.getTotalCount().longValue());
        return page;
    }

    @Override
    public MemberRes getUserInfo() {
        MapBuilder mapBuilder = MapUtils.builder().field(MemberRes::getId, StpUtil.getLoginId());
        return beanSearcher.searchFirst(MemberRes.class, mapBuilder.build());
    }

    private static final String AVATARURL="https://oss.xinyanbao.cn/image/headPortrait.png";

    @Override
    public MemberRes login(String phone, String password) {
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getPhone, phone);
        Member member = this.getOne(eq);
        if(ObjectUtil.isEmpty(member) || !MD5.aesEncrypt(member.getPhone(),password).equals(member.getPassword())){
            throw new ResultException(ResultCodeEnum.ERROR_USER_NAME_PASS.msg());
        }
        member.setLoginTime(LocalDateTime.now());
        this.updateById(member);
        return Convert.convert(MemberRes.class,member);
    }

    @Override
    public void registerUser(SendPhoneReq sendPhoneReq) {
        verificationCode(sendPhoneReq.getPhone(), sendPhoneReq.getCode());
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getPhone, sendPhoneReq.getPhone());
        Member member = this.getOne(eq);
        if(ObjectUtil.isNotEmpty(member)){
            throw new ResultException("手机号码已存在,请重新输入手机号码");
        }
        member=new Member();
        member.setIdNum(RandomUtil.randomNumbers(5));
        member.setNickName("用户"+RandomUtil.randomString(6));
        member.setPassword(MD5.aesEncrypt(sendPhoneReq.getPhone(),sendPhoneReq.getPassword()));
        member.setRuleId(1);
        member.setPhone(sendPhoneReq.getPhone());
        member.setAvatarUrl(AVATARURL);
        this.save(member);
    }

    private void verificationCode(String phone, String code){
        String phoneCache = redisCache.get(Constants.SMS_ + phone);
        if(StrUtil.isEmpty(phoneCache)){
            throw new ResultException("验证码已过期,请重新获取");
        }
        if(!phoneCache.equals(code)){
            throw new ResultException("验证码输入错误,请重新输入");
        }
    }

    @Override
    public void editUserPassword(SendPhoneReq sendPhoneRe) {
        verificationCode(sendPhoneRe.getPhone(), sendPhoneRe.getCode());
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getPhone, sendPhoneRe.getPhone());
        Member member = this.getOne(eq);
        if(ObjectUtil.isEmpty(member)){
           throw new ResultException("手机号码不存在,请注册");
       }
       member.setPassword(MD5.aesEncrypt(member.getPhone(),sendPhoneRe.getPassword()));
       this.updateById(member);

    }

    private final SysLogService sysLogService;

    @Override
    public WebMemberRes saveWxMember(String openId,String unionId) {
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getUnionId, unionId);
        Member member = this.getOne(eq);
        if(ObjectUtil.isEmpty(member)){
            member=new Member();
            member.setIdNum(RandomUtil.randomNumbers(5));
            member.setNickName("用户"+RandomUtil.randomString(6));
            member.setOpenId(openId);
            member.setIsWx(1);
            member.setRuleId(1);
            member.setOpenId(openId);
            member.setUnionId(unionId);
            member.setAvatarUrl(AVATARURL);
            this.save(member);
        }
        member.setLoginTime(LocalDateTime.now());
        this.updateById(member);
        StpUtil.login(member.getId(),"web");
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        if(StrUtil.isNotEmpty(SaManager.getConfig().getTokenPrefix())){
            tokenInfo.setTokenValue(SaManager.getConfig().getTokenPrefix()+" "+tokenInfo.getTokenValue());
        }
        WebMemberRes memberRes = Convert.convert(WebMemberRes.class, member);
        memberRes.setTokenInfo(tokenInfo);
        //保存登录日志
        HttpServletRequest request = ((ServletRequestAttributes)Convert.convert(ServletRequestAttributes.class, RequestContextHolder.getRequestAttributes())).getRequest();
        String ip = request.getRemoteAddr();
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("user-agent"));
        log.info("userAgent:{}", JSON.toJSONString(userAgent));
        String versionNum = userAgent.getVersion();
        String browserName = userAgent.getBrowser().toString();
        SysLog sysLog=new SysLog(versionNum,browserName,ip,StpUtil.getLoginIdAsInt(),"PC",null,"");
        sysLogService.save(sysLog);
        return memberRes;
    }

    @Override
    public void editNickName(String nickName, Integer userId) {
        Member member=new Member();
        member.setId(userId);
        member.setNickName(nickName);
        this.updateById(member);
    }

    @Override
    public void changePhone(WebMemberReq webMemberRes) {
        verificationCode(webMemberRes.getPhone(), webMemberRes.getCode());
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getPhone, webMemberRes.getPhone());
        Member member = this.getOne(eq);
        if(ObjectUtil.isNotEmpty(member)) {
            throw new ResultException("手机号码已存在");
        }
        Member member1=new Member();
        member1.setId(StpUtil.getLoginIdAsInt());
        member1.setPhone(webMemberRes.getPhone());
        this.updateById(member1);
    }

    @Override
    public void updateAvatarUrl(WebMemberReq webMemberRes) {
        Member member=new Member();
        member.setId(webMemberRes.getUserId());
        member.setAvatarUrl(webMemberRes.getAvatarUrl());
        this.updateById(member);
    }

    private final BeanSearcher beanSearcher;

    @Override
    public MemberRes findById(Integer convert) {
        Map<String, Object> build = MapUtils.builder().field(MemberRes::getId, convert).build();
       return beanSearcher.searchFirst(MemberRes.class, build);
    }

    private final OssConfigure ossConfigure;

    private final MemberUploadService memberUploadService;

    @Override
    public void uploadPdf(MultipartFile file) {
        Long datePdfNum = memberUploadService.findDatePdfNum();
        if(datePdfNum>50){
            throw new ResultException("当天上传文件达到限制");
        }
        String afterName = FileUtil.extName(file.getOriginalFilename());
        assert afterName != null;
        if (!afterName.toLowerCase().equals(FileEnum.PDF.name().toLowerCase())) {
            throw new ResultException(file.getOriginalFilename()+"文件格式不对");
        }
       String filePath=  ossConfigure.getAccessUrl()+ossConfigure.uploadFile(file);
       memberUploadService.saveMemberUpload(filePath,file.getOriginalFilename());
    }

    @Override
    public MemberRes memberInfo() {
        return getMemberById(StpUtil.getLoginIdAsInt());
    }


    @Override
    public MemberRes getByMemberInfo(Integer memberId) {
        MapBuilder op = MapUtils.builder().field(MemberRes::getId, memberId).op(Equal.class);
        return beanSearcher.searchFirst(MemberRes.class, op.build());

    }

    @Override
    public Boolean updateWxMember(String fromUser) {
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getOpenId, fromUser);
        Member member = this.getOne(eq);
        if(ObjectUtil.isNotEmpty(member) ){
             return true;
        }
        member.setIsWx(1);
        member.setOpenId(fromUser);
        this.updateById(member);
        return false;

    }


    @Override
    public void saveMember(Member convert) {
        convert.setPassword(MD5.aesEncrypt(convert.getPhone(), Constants.DEFAULT_PASS_WORD));
        convert.setIdNum(RandomUtil.randomNumbers(5));
        this.save(convert);
    }

    @Override
    public void updateMemberState(Integer id) {
        Member member = this.getById(id);
        if(ObjectUtil.isNotEmpty(member)){
            member.setMemberState(0);
            member.setRuleId(1);
            member.setEffectiveTime(null);
            this.updateById(member);
        }
    }

    @Override
    public MemberRes getMemberById(Integer id) {
        Map<String, Object> build = MapUtils.builder().field(MemberRes::getId, id).op(Equal.class).build();
       return beanSearcher.searchFirst(MemberRes.class, build);
    }

    @Override
    public List<ReadRankingRes> readRankingList(ReadRankingReq readRankingReq) {
        Map<String, Object> build = MapUtils.builder().build();
        SearchResult<ReadRankingRes> search = beanSearcher.search(ReadRankingRes.class, build);
        return search.getDataList();
    }

    @Override
    public ReadRankingRes readRanking(ReadRankingReq readRankingReq) {
        Map<String, Object> build = MapUtils.builder().field(ReadRankingRes::getId, readRankingReq.getMemberId()).build();
        return beanSearcher.searchFirst(ReadRankingRes.class, build);
    }

    private final ICollectService collectService;

    @Override
    public void saveCollect(WebMemberCollectReq webMemberCollectReq) {
        LambdaQueryWrapper<Collect> eq = Wrappers.lambdaQuery(Collect.class).eq(Collect::getReportId, webMemberCollectReq.getReportId())
                .eq(Collect::getMemberId, webMemberCollectReq.getMemberId());
        Collect one = collectService.getOne(eq);
        if(ObjectUtil.isEmpty(one)){
            one = new Collect();
            one.setMemberId(webMemberCollectReq.getMemberId());
            one.setReportId(webMemberCollectReq.getReportId());
            one.setSeparate(1);
            collectService.save(one);
        }
    }

    @Override
    public void saveReadRanking(ReadRankingReq readRankingReq) {
        Member member = this.getById(readRankingReq.getMemberId());
        if(readRankingReq.getIsSelected()){
            member.setZanNum(member.getZanNum()+1);
        }else{
            member.setZanNum(member.getZanNum()-1);
        }
        this.updateById(member);
    }

    @Override
    public MemberRes modifyMember(MemberReq memberReq) {
        Member member=new Member();
        member.setId(memberReq.getId());
        member.setAvatarUrl(memberReq.getAvatarUrl());
        member.setNickName(memberReq.getNickName());
        this.updateById(member);
        return this.findById(memberReq.getId());
    }


    @Override
    public void updateMemberInfo(MemberReq memberReq) {
        LocalDate effectiveTime = memberReq.getEffectiveTime();
        if(memberReq.getEffectiveTime().compareTo(effectiveTime)<0){
            throw new ResultException("选择的会员有效时间不能小于当前会员时间");
        }
        Member convert = Convert.convert(Member.class, memberReq);
        convert.setMemberState(1);
        this.updateById(convert);
        redisCache.delete(TagConstants.KEY_EXPIRATION+memberReq.getId());
        redisCache.set(TagConstants.KEY_EXPIRATION+memberReq.getId(), String.valueOf(memberReq.getId()),LocalDateUtils.day(memberReq.getEffectiveTime()), TimeUnit.DAYS);
    }

    @Override
    public PanelGroupRes panelGroup() {
        PanelGroupRes panelGroupRes = new PanelGroupRes();
        long userCount = this.count();
        panelGroupRes.setUserCount(userCount);
        LambdaQueryWrapper<Member> eq = Wrappers.lambdaQuery(Member.class).eq(Member::getMemberState, Constants.ONE);
        long memberCount = this.count(eq);
        panelGroupRes.setMemberCount(memberCount);
        BigDecimal today=this.baseMapper.findTodayEarningsCount();
        panelGroupRes.setTodayEarningsCount(today);
        BigDecimal totalPrice=this.baseMapper.totalPrice();
        panelGroupRes.setTotalPrice(totalPrice);
        Integer loginNum=this.baseMapper.loginNum();
        panelGroupRes.setLoginNum(loginNum);
        return panelGroupRes;
    }

    @Override
    public LineChartRes lineChart() {
        List<LocalDate> localDates = LocalDateUtils.beforeDate();
        List<Integer> userCountLists= ListUtil.list(true);
        List<Integer> memberCountLists= ListUtil.list(true);
        List<BigDecimal> priceLists= ListUtil.list(true);
        localDates.forEach(localDate -> {
            Integer userCount=this.baseMapper.findUserCount(LocalDateTimeUtil.formatNormal(localDate));
            userCountLists.add(userCount);
            Integer memberCount=this.baseMapper.memberCount(LocalDateTimeUtil.formatNormal(localDate));
            memberCountLists.add(memberCount);
            BigDecimal price=this.baseMapper.findTodayCount(LocalDateTimeUtil.formatNormal(localDate));
            priceLists.add(price);
        });
        LineChartRes lineChartRes = new LineChartRes();
        lineChartRes.setLocalDates(localDates);
        lineChartRes.setPriceLists(priceLists);
        lineChartRes.setUserCountLists(userCountLists);
        lineChartRes.setMemberCountLists(memberCountLists);
        return lineChartRes;
    }


}
